/*
 * Tencent is pleased to support the open source community by making ovCompose available.
 * Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

@file:OptIn(ExperimentalForeignApi::class)

package com.tencent.compose.utils

import androidx.compose.ui.graphics.kLog
import androidx.compose.ui.graphics.isDebugLogEnabled
import androidx.compose.ui.napi.JsCallContext
import kotlinx.cinterop.COpaquePointer
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.StableRef
import kotlinx.cinterop.asStableRef
import kotlinx.cinterop.cValuesOf
import kotlinx.cinterop.get
import kotlinx.cinterop.staticCFunction
import kotlinx.cinterop.toKString
import platform.ohos.napi_acquire_threadsafe_function
import platform.ohos.napi_call_threadsafe_function
import platform.ohos.napi_callback
import platform.ohos.napi_callback_info
import platform.ohos.napi_delete_reference
import platform.ohos.napi_env
import platform.ohos.napi_ok
import platform.ohos.napi_ref
import platform.ohos.napi_set_element
import platform.ohos.napi_set_property
import platform.ohos.napi_status
import platform.ohos.napi_threadsafe_function
import platform.ohos.napi_threadsafe_function_call_js
import platform.ohos.napi_threadsafe_function_call_mode
import platform.ohos.napi_value
import platform.ohos.napi_valuetype
import platform.napiwrapper.*

object TsEnv {

    private var globalNApiEnv: napi_env? = null

    fun init(env: napi_env) {
        globalNApiEnv = env
    }

    fun env(): napi_env = globalNApiEnv ?: error("napi_env is not initialized. Call JsEnv.init(env) first.")

    private fun checkStatus(status: napi_status): Boolean {
        if (status != napi_ok) {
            if (isDebugLogEnabled) {
                kLog("napi failed: $status")
            }
            return false
        }
        return true
    }

    fun getElement(obj: napi_value?, index: Int): napi_value? {
        obj ?: return null
        return kn_get_element(env(), obj, index)
    }

    fun setElement(obj: napi_value?, index: Int, value: napi_value?) {
        obj ?: return

        checkStatus(napi_set_element(env(), obj, index.toUInt(), value))
    }

    fun getArrayLength(array: napi_value?): Int {
        array ?: return 0
        return kn_get_array_length(env(), array).toInt()
    }

    fun getAllPropertyNames(obj: napi_value?): napi_value? {
        return kn_get_property_names(env(), obj)
    }

    fun strictEquals(trgObject: napi_value?, srcObject: napi_value?): Boolean {
        return kn_equals(env(), trgObject, srcObject)
    }

    fun getProperty(obj: napi_value?, key: napi_value?): napi_value? {
        obj ?: return null
        key ?: return null
        return kn_get_property(env(), obj, key)
    }

    fun setProperty(obj: napi_value?, key: napi_value?, value: napi_value?) {
        obj ?: return
        key ?: return
        checkStatus(napi_set_property(env(), obj, key, value ?: getNull()))
    }

    fun getNamedProperty(obj: napi_value?, str: String): napi_value? {
        obj ?: return null
        return kn_get_named_property(env(), obj, str)
    }

    fun createObject(): napi_value? {
        return kn_create_object(env())
    }

    fun getReferenceValue(ref: napi_ref?): napi_value? {
        ref ?: return null
        return kn_get_reference_value(env(), ref)
    }

    fun createReference(value: napi_value?): napi_ref? {
        return kn_create_reference(env(), value)
    }

    fun deleteReference(ref: napi_ref?) {
        ref ?: return
        checkStatus(napi_delete_reference(env(), ref))
    }

    fun createFunction(name: String?, cb: napi_callback?, data: COpaquePointer?): napi_value? {
        return kn_create_function(env(), name, cb, data)
    }

    fun callFunction(
        receiver: napi_value?,
        func: napi_value?,
        vararg args: napi_value?
    ): napi_value? {
        func ?: return null
        return kn_call_function(
            env(),
            receiver,
            func,
            args.size,
            cValuesOf(*args),
            null
        )
    }

    fun createStringUtf8(str: String): napi_value? {
        return kn_string_to_napi(env(), str)
    }

    fun createDouble(value: Double): napi_value? {
        return kn_double_to_napi(env(), value)
    }

    fun createFloat(value: Float): napi_value? = createDouble(value.toDouble())

    fun createObjectWithWrap(value: Any): napi_value? {
        val obj = kn_create_object(env())
        val valueRef = StableRef.create(value)
        kn_wrap(
            env(),
            obj,
            valueRef.asCPointer(),
            staticCFunction { _: napi_env?, data: COpaquePointer?, _: COpaquePointer? ->
                if (data == null) return@staticCFunction
                data.asStableRef<Any>().dispose()
            })
        return obj
    }

    fun getBoolean(value: Boolean): napi_value? {
        return kn_boolean_to_napi(env(), value)
    }

    fun createInt32(value: Int): napi_value? {
        return kn_int_to_napi(env(), value)
    }

    fun createInt64(value: Long): napi_value? {
        return kn_long_to_napi(env(), value)
    }

    fun getValueInt32(value: napi_value?): Int? {
        value ?: return null
        return kn_to_int(env(), value)
    }

    fun getValueInt32(value: napi_value?, default: Int): Int {
        value ?: return default
        return kn_to_int(env(), value) ?: default
    }

    fun getValueInt64(value: napi_value?): Long? {
        value ?: return null
        return kn_to_long(env(), value)
    }

    fun getValueInt64(value: napi_value?, default: Long): Long {
        value ?: return default
        return kn_to_long(env(), value) ?: default
    }

    fun getValueDouble(value: napi_value?): Double? {
        value ?: return null
        return kn_to_double(env(), value)
    }

    fun getValueFloat(value: napi_value?): Float? = getValueDouble(value)?.toFloat()

    fun getValueStringUtf8(value: napi_value?): String? {
        value ?: return null
        return kn_to_string(env(), value)?.toKString()
    }

    fun getValueBool(value: napi_value?): Boolean? {
        value ?: return null
        return kn_to_boolean(env(), value)
    }

    fun getUndefined(): napi_value? {
        return kn_get_undefined(env())
    }

    fun isUndefined(value: napi_value?): Boolean =
        getType(value) == napi_valuetype.napi_undefined

    private fun getNull(): napi_value? {
        return kn_get_null(env())
    }

    fun getType(value: napi_value?): napi_valuetype? {
        return kn_typeof(env(), value)
    }

    fun getCbInfo(callbackInfo: napi_callback_info?): JsCallContext {
        val argc = kn_get_cb_info_argc(env(), callbackInfo)
        val args = kn_get_cb_info_args(env(), callbackInfo, argc)
        val jsThis = kn_get_cb_info_jsThis(env(), callbackInfo)
        val data = kn_get_cb_info_data(env(), callbackInfo)
        val arguments = ArrayList<napi_value?>()

        for (i in 0..<argc) {
            arguments.add(args?.get(i))
        }
        return JsCallContext(jsThis, arguments, data)
    }

    fun getGlobal(): napi_value? {
        return kn_get_global(env())
    }

    fun createThreadsafeFunction(
        workName: String,
        callback: napi_threadsafe_function_call_js?
    ): napi_threadsafe_function? {
        return kn_create_threadsafe_function_with_callback(
            env(),
            workName,
            callback
        )
    }

    fun callThreadsafeFunction(function: napi_threadsafe_function?, data: COpaquePointer?) {
        napi_acquire_threadsafe_function(function)
        napi_call_threadsafe_function(
            function, data, napi_threadsafe_function_call_mode.napi_tsfn_nonblocking
        )
    }

    fun stringify(value: napi_value?): String? {
        val global = getGlobal()
        val json = getProperty(global, createStringUtf8("JSON"))
        val stringifyFunc = getProperty(json, createStringUtf8("stringify"))
        val result = callFunction(json, stringifyFunc, value)
        return getValueStringUtf8(result)
    }

    fun printValue(value: napi_value?, key: String? = "") {
        val type = getType(value)
        when (type) {
            napi_valuetype.napi_undefined -> kLog("JsEnv::printValue: $key is undefined")
            napi_valuetype.napi_null -> kLog("JsEnv::printValue: $key is null")
            napi_valuetype.napi_symbol -> kLog("JsEnv::printValue: $key is symbol")
            napi_valuetype.napi_object -> kLog("JsEnv::printValue: $key is object")
            napi_valuetype.napi_function -> kLog("JsEnv::printValue: $key is function")
            napi_valuetype.napi_number -> kLog("JsEnv::printValue: $key is number")
            napi_valuetype.napi_string -> kLog("JsEnv::printValue: $key is string")
            napi_valuetype.napi_boolean -> kLog("JsEnv::printValue: $key is boolean")
            napi_valuetype.napi_bigint -> kLog("JsEnv::printValue: $key is bigint")
            napi_valuetype.napi_external -> kLog("JsEnv::printValue: $key is external")
            null -> kLog("JsEnv::printValue: $key is null")
            else -> {}
        }
    }
}